﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using GLWrap;
using GLWrap.SceneObjects;
using ReAnimation.Domain;
using ReAnimation.Helpers.Common;
using ReAnimation.Helpers.Drawing;
using ReAnimation.Helpers.WinForms;
using Sharp3D.Math.Core;
using GLWrap.SceneObjects.Line;

namespace ReAnimation.Graphics
{
    public class MoleculeDrawer : IMoleculeDrawer
    {
        public Color SelectedAtomColor
        {
            get
            { return _selectedAtomColor; }
            set
            {
                if (_selectedAtomColor.Equals(value))
                    return;

                _selectedAtomColor = value;
                _selectedMaterial = _materialFactory.CreateMaterial(value);
            }
        }

        public IGlSceneManager GlSceneManager { get; private set; }
        public Molecule Molecule
        {
            get
            {
                return _molecule;
            }
            set
            {
                if (_molecule.Equals(value))
                    return;
                _molecule = value;
                SynchronizeMoleculeWithModel();
            }
        }

        public IDictionary<SceneObject, Atom> SceneToAtomMap
        {
            get { return new Dictionary<SceneObject, Atom>(_sceneToAtomMapping); }
        }

        public IDictionary<SceneObject, Link> SceneToLinkMap
        {
            get { return new Dictionary<SceneObject, Link>(_sceneToLinkMapping); }
        }

        public IDictionary<Atom, AtomEmphasizeParams> EmphasizedAtoms
        {
            get { return new Dictionary<Atom, AtomEmphasizeParams>(_emphasizedAtoms); }
        }

        public void EmphasizeAtom(Atom atom, AtomEmphasizeParams atomEmphasizeParams)
        {
            SceneObject sceneObject = GetSceneObjectByAtom(atom);
            if (sceneObject == null) return;
            sceneObject.Material = _materialFactory.CreateMaterial(atomEmphasizeParams.Color);
            GlSceneManager.Refresh();
        }

        private SceneObject GetSceneObjectByAtom(Atom atom)
        {
            var selectedObjects = from pair in _sceneToAtomMapping
                                  where pair.Value == atom
                                  select pair.Key;

            return selectedObjects.FirstOrDefault();
        }

        public void DeemphasizeAtom(Atom atom)
        {
            SceneObject sceneObject = GetSceneObjectByAtom(atom);
            if (sceneObject == null) return;
            sceneObject.Material = _materialFactory.CreateMaterialForAtom(atom);
            GlSceneManager.Refresh();
        }

        public void EmphasizeAtoms(IDictionary<Atom, AtomEmphasizeParams> atomSelections)
        {
            foreach (var paramse in atomSelections)
                EmphasizeAtom(paramse.Key, paramse.Value);
        }

        public void DeemphasizeAtoms(IEnumerable<Atom> atoms)
        {
            foreach (var atom in atoms)
                DeemphasizeAtom(atom);
        }

        public event EventHandler<EventArg<Atom>> AtomSelected;

        private MaterialFactory _materialFactory;
        private Material _selectedMaterial;
        private Material _normalLinkMaterial;
        private Color _selectedAtomColor;
        private IDictionary<SceneObject, Atom> _sceneToAtomMapping;
        private IDictionary<SceneObject, Link> _sceneToLinkMapping;
        private IDictionary<Atom, AtomEmphasizeParams> _emphasizedAtoms;
        private ILinkSceneObjectsFactory _linkSceneObjectsFactory;
        
        private Molecule _molecule;

        public MoleculeDrawer(ILinkSceneObjectsFactory linkSceneObjectsFactory)
        {
            _materialFactory = new MaterialFactory();
            _linkSceneObjectsFactory = linkSceneObjectsFactory;
            _molecule = Molecule.NullMolecule();
            _sceneToAtomMapping = new Dictionary<SceneObject, Atom>();
            _sceneToLinkMapping = new Dictionary<SceneObject, Link>();
            _emphasizedAtoms = new Dictionary<Atom, AtomEmphasizeParams>();


            SelectedAtomColor = Color.Blue;

            _normalLinkMaterial = _materialFactory.CreateMaterial(Color.Green);
            _selectedMaterial = _materialFactory.CreateMaterial(SelectedAtomColor, 85);
            _linkSceneObjectsFactory.DefaultMaterial = _normalLinkMaterial;
        }

        private void SynchronizeMoleculeWithModel()
        {
            RemoveUnusedObjects();
            AddNewObjects();
        }

        private void AddNewObjects()
        {
            var newAtoms = from a in _molecule.Atoms
                           where !_sceneToAtomMapping.Values.Contains(a)
                           select a;
            var newLinks = from link in _molecule.Links
                           where !_sceneToLinkMapping.Values.Contains(link) 
                                 &&link.LinkType != MoleculeLinkType.None
                           select link;

            AddAtomsToScene(newAtoms);
            AddLinksToScene(newLinks);
        }

        private void AddLinksToScene(IEnumerable<Link> newLinks)
        {
            foreach (var link in newLinks)
            {
                SceneObject linkSceneObject = _linkSceneObjectsFactory.GetSceneObjectForLinkType(link);
               
                GlSceneManager.AddSceneObject(linkSceneObject);
                _sceneToLinkMapping.Add(linkSceneObject, link);
            }
        }

        private void AddAtomsToScene(IEnumerable<Atom> newAtoms)
        {
            foreach (var atom in newAtoms)
            {
                SceneObject sceneObject = new Sphere
                                            {
                                                Material = _materialFactory.CreateMaterialForAtom(atom),
                                                Position = atom.Position,
                                                Radius = 0.0578f * atom.AtomDescription.Radius
                                            };
                GlSceneManager.AddSceneObject(sceneObject);
                _sceneToAtomMapping.Add(sceneObject, atom);
            }
        }

        private void RemoveUnusedObjects()
        {
            var unusedSceneAtoms = (from objectToAtomMap in _sceneToAtomMapping
                                    where !_molecule.Atoms.Contains(objectToAtomMap.Value)
                                    select objectToAtomMap.Key).ToArray();

            var unusedSceneLinks = (from objectToLinkMap in _sceneToLinkMapping
                                    where 
                                    !_molecule.Links.Contains(objectToLinkMap.Value)
                                    || objectToLinkMap.Value.LinkType == MoleculeLinkType.None
                                    || !_linkSceneObjectsFactory.CheckSceneObjectIsLinkType(objectToLinkMap.Value.LinkType,objectToLinkMap.Key)
                                    select objectToLinkMap.Key).ToArray();


            unusedSceneAtoms.ForEach(o =>
                                         {
                                             _sceneToAtomMapping.Remove(o);
                                             GlSceneManager.RemoveSceneObject(o);
                                         });

            unusedSceneLinks.ForEach(o =>
                                         {
                                             _sceneToLinkMapping.Remove(o);
                                             GlSceneManager.RemoveSceneObject(o);
                                         });
        }

        public void Init(IGlSceneManager glSceneManager)
        {
            GlSceneManager = glSceneManager;
        }

        public void Update()
        {
            SynchronizeMoleculeWithModel();
            GlSceneManager.Refresh();
        }
    }
}